﻿// ----------------------------------------------------------------------------
//  Copyright (C) 成都联宇创新科技有限公司 版权所有。 
//      
//  文件名：WindowsInteropProvider.cs
//  文件功能描述：
//  创建标识：骆智慧 2016/03/02 23:28
//     
//  修改标识：骆智慧  2016/03/10/06:40
//  修改描述：
//  ----------------------------------------------------------------------------

#region 导入名称空间

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Principal;

#endregion

namespace LightWork.Provideres
{
    /// <summary>
    /// Windows互操作提供者，用此提供者类可以实现与桌面交互
    /// </summary>
    public class WindowsInteropProvider
    {
        /// <summary>
        /// 显示弹出窗口
        /// </summary>
        /// <param name="title">标题</param>
        /// <param name="message">内容</param>
        public static void ShowMessageBox(string title, string message)
        {
            int resp;
            var sessionId = WTSGetActiveConsoleSessionId();
            WTSSendMessage(
                WtsCurrentServerHandle,
                sessionId,
                title, title.Length,
                message, message.Length,
                0, 0, out resp, false);
        }


        /// <summary>
        /// 创建并运行交互进程 在交互进程退出时结束，不支持异步
        /// </summary>
        /// <param name="appName">文件名</param>
        /// <param name="appPath">文件所在目录路径</param>
        public static void CreateAndRunProcess(string appName, string appPath)
        {
            CreateAndRunProcess(appName, appName, string.Empty);
        }

        /// <summary>
        /// 创建并运行交互进程 在交互进程退出时结束，不支持异步
        /// </summary>
        /// <param name="appName">文件名</param>
        /// <param name="appPath">文件所在目录路径</param>
        /// <param name="commandLine">命令行代码</param>
        public static void CreateAndRunProcess(string appName, string appPath, string commandLine)
        {
            var windowsIdentity = WindowsIdentity.GetCurrent();
            if (windowsIdentity == null) return;

            var hDupedToken = IntPtr.Zero;

            var processInfo = new ProcessInformation();
            var securityAttr = new SecurityAttributes();
            securityAttr.Length = Marshal.SizeOf(securityAttr);

            //设置进程启动信息
            var startupInfo = new Startupinfo();
            startupInfo.cb = Marshal.SizeOf(startupInfo); //这个是必须的

            //获得当前Session ID
            var dwSessionId = WTSGetActiveConsoleSessionId();

            IntPtr hToken;
            // 获得当前Session的用户令牌
            WTSQueryUserToken(dwSessionId, out hToken);
            // 复制令牌
            DuplicateTokenEx(
                hToken,
                GENERIC_ALL_ACCESS,
                ref securityAttr,
                (int) SecurityImpersonationLevel.SecurityIdentification,
                (int) TokenType.TokenPrimary,
                ref hDupedToken
            );

            // 创建用户Session环境
            IntPtr lpEnvironment;
            CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);

            try
            {
                // 在复制的用户Session下执行应用程序，创建进程。
                // 通过这个进程，就可以显示各种复杂的用户界面了
                if (CreateProcessAsUser(
                    hDupedToken,
                    Path.Combine(appPath, appName),
                    commandLine,
                    ref securityAttr, ref securityAttr,
                    false, 0, IntPtr.Zero,
                    appPath, ref startupInfo, ref processInfo))
                {
                    var p = Process.GetProcessById(processInfo.dwProcessID);
                    p.EnableRaisingEvents = true;
                    p.Exited += p_Exited;
                    p.WaitForExit();
                }
                else
                {
                    var error = Marshal.GetLastWin32Error();
                    var message = string.Format("创建用户进程 错误: {0}", error);
                    ShowMessageBox(message, "错误提示！");
                }
            }
            catch (Exception ex)
            {
                ShowMessageBox("错误提示！", ex.Message);
            }
            finally
            {
                //清理工作
                if (hDupedToken != IntPtr.Zero)
                    CloseHandle(hDupedToken);

                if (processInfo.hProcess != IntPtr.Zero)
                    CloseHandle(processInfo.hProcess);

                if (processInfo.hThread != IntPtr.Zero)
                    CloseHandle(processInfo.hThread);

                if (lpEnvironment != IntPtr.Zero)
                    DestroyEnvironmentBlock(lpEnvironment);
            }
        }

        private static void p_Exited(object sender, EventArgs e)
        {
            var p = sender as Process;
            if (p != null) p.Dispose();
        }

        #region 非托管函数包装

        /// <summary>
        /// 定义当前服务句柄
        /// </summary>
        internal static IntPtr WtsCurrentServerHandle = IntPtr.Zero;

        #region 常量定义

        /// <summary>
        /// 通用访问权限设定
        /// </summary>
        internal const int GENERIC_ALL_ACCESS = 0x10000000;

        #region STARTINFO dwFlag 取值

        // STARTF_USEFILLATTRIBUTE -> 0x00000010
        /// <summary>
        /// 使用d w F i l l A t t r i b u t e 成员
        /// </summary>
        internal const int STARTF_USEFILLATTRIBUTE = 16;

        /// STARTF_FORCEOFFFEEDBACK -> 0x00000080
        internal const int STARTF_FORCEOFFFEEDBACK = 128;

        /// STARTF_FORCEONFEEDBACK -> 0x00000040
        internal const int STARTF_FORCEONFEEDBACK = 64;

        // STARTF_USESTDHANDLES -> 0x00000100
        /// <summary>
        /// 使用hStdInput 、hStdOutput 和hStdError 成员
        /// </summary>
        internal const int STARTF_USESTDHANDLES = 256;

        /// <summary>
        /// 使用w S h o w Wi n d o w 成员
        /// STARTF_USESHOWWINDOW -> 0x00000001
        /// </summary>
        internal const int STARTF_USESHOWWINDOW = 1;

        //STARTF_USECOUNTCHARS -> 0x00000008
        /// <summary>
        /// 使用dw X C o u n t C h a r s 和dwYCount Chars 成员
        /// </summary>
        internal const int STARTF_USECOUNTCHARS = 8;

        /// STARTF_RUNFULLSCREEN -> 0x00000020
        internal const int STARTF_RUNFULLSCREEN = 32;

        // STARTF_USEPOSITION -> 0x00000004
        /// <summary>
        /// 使用d w X 和d w Y 成员
        /// </summary>
        internal const int STARTF_USEPOSITION = 4;

        /// STARTF_USEHOTKEY -> 0x00000200
        internal const int STARTF_USEHOTKEY = 512;

        /// <summary>
        /// 使用dwXSize 和dwYSize 成员
        /// STARTF_USESIZE -> 0x00000002
        /// </summary>
        internal const int STARTF_USESIZE = 2;

        #endregion

        #region 标准输入输出句柄

        internal const int STD_ERROR_HANDLE = -12;
        internal const int STD_INPUT_HANDLE = -10;
        internal const int STD_OUTPUT_HANDLE = -11;

        #endregion

        #endregion

        #region 结构体包装

        /// <summary>
        /// 启动信息结构体
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct Startupinfo
        {
            /// <summary>
            /// 包含STARTUPINFO结构中的字节数
            /// 必须将cb初始化为sizeof(STARTUPINFO)
            /// </summary>
            internal int cb;

            /// <summary>
            /// 保留。必须初始化为NULL
            /// </summary>
            internal string lpReserved;

            /// <summary>
            /// 用于标识启动应用程序所在的桌面的名字。
            /// 如果该桌面存在，新进程便与指定的桌面相关联。
            /// 如果桌面不存在，便创建一个带有默认属性的桌面，并使用为新进程指定的名字
            /// 如果lpDesktop是NULL（这是最常见的情况),那么该进程将与当前桌面相关联
            /// </summary>
            internal string lpDesktop;

            /// <summary>
            /// 用于设定控制台窗口的名称。
            /// 如果lpTitle 是NULL ，则可执行文件的名字将用作窗口名
            /// </summary>
            internal string lpTitle;

            /// <summary>
            /// 指定新窗口左上角的x 和y 偏移量(以像素为单位)。
            /// 如果dwFlags 成员未指定STARTF_USEPOSITION 标志，则忽略这两项。
            /// </summary>
            internal int dwX;

            internal int dwY;

            /// <summary>
            /// 指定新窗口的宽度和高度。
            /// 如果dwFlags 成员未指定STARTF_USESIZE 标志，则忽略这两个成员。
            /// </summary>
            internal int dwXSize;

            internal int dwYSize;

            /// <summary>
            /// 指定新控制台窗口的屏幕缓冲区的宽度和高度。
            /// 如果dwFlags 成员未指定STARTF_USECOUNTCHARS 标志，则忽略这两成员。
            /// </summary>
            internal int dwXCountChars;

            internal int dwYCountChars;

            /// <summary>
            /// 指定新控制台窗口的初始文字和背景颜色。
            /// 如果dwFlags 成员未指定STARTF_USEFILLATTRIBUTE 标志，则忽略该成员。
            /// </summary>
            internal int dwFillAttribute;

            /// <summary>
            /// 创建窗口标志。
            /// </summary>
            internal int dwFlags;

            /// <summary>
            /// 新窗口的显示状态。
            /// 如果dwFlags 成员未指定STARTF_USESHOWWINDOWW 标志，则忽略该成员。
            /// </summary>
            internal short wShowWindow;

            /// <summary>
            /// 保留。必须被初始化为0
            /// </summary>
            internal short cbReserved2;

            /// <summary>
            /// 保留。必须被初始化为NULL
            /// </summary>
            internal IntPtr lpReserved2;

            /// <summary>
            /// 指定一个句柄，该句柄用作进程的标准输入句柄。
            /// 如果dwFlags 成员未指定STARTF_USESTDHANDLES 标志，则忽略该成员。
            /// </summary>
            internal IntPtr hStdInput;

            /// <summary>
            /// 指定一个句柄，该句柄用作进程的标准输出句柄。
            /// 如果dwFlags 成员未指定STARTF_USESTDHANDLES，则忽略该成员。
            /// </summary>
            internal IntPtr hStdOutput;

            /// <summary>
            /// 指定一个句柄，该句柄用作进程的标准错误句柄。
            /// 如果dwFlags 成员未指定STARTF_USESTDHANDLES，则忽略该成员。
            /// </summary>
            internal IntPtr hStdError;
        }

        /// <summary>
        /// 进程消息结构体
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct ProcessInformation
        {
            /// <summary>
            /// 返回新进程的句柄
            /// </summary>
            internal IntPtr hProcess;

            /// <summary>
            /// 返回主线程的句柄。
            /// </summary>
            internal IntPtr hThread;

            /// <summary>
            /// 返回一个全局进程标识符。该标识符用于标识一个进程。从进程被创建到终止，该值始终有效。
            /// </summary>
            internal int dwProcessID;

            /// <summary>
            /// 返回一个全局线程标识符。该标识符用于标识一个线程。从线程被创建到终止，该值始终有效。
            /// </summary>
            internal int dwThreadID;
        }

        /// <summary>
        /// 安全特性结构体
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct SecurityAttributes
        {
            /// <summary>
            /// 指定该结构大小。
            /// </summary>
            internal int Length;

            /// <summary>
            /// 指向一个对象的安全描述符，该安全描述符控制对象的共享。
            /// 如果该成员置为NULL，则该对象使用调用进程的默认安全描述符。
            /// </summary>
            internal IntPtr lpSecurityDescriptor;

            /// <summary>
            /// 指定新进程被创建时是否继承返回的句柄。
            /// 若该成员置为TRUE，则新进程继承该句柄。
            /// </summary>
            internal bool bInheritHandle;
        }

        #endregion

        #region 枚举包装

        /// <summary>
        /// 安全模拟级别
        /// </summary>
        internal enum SecurityImpersonationLevel
        {
            /// <summary>
            /// 匿名 无法获取有关客户端的标识信息，且无法模拟客户端
            /// </summary>
            SecurityAnonymous,

            /// <summary>
            /// 识别 可以获取有关客户端的信息（如安全标识符和特权），但是无法模拟客户端
            /// </summary>
            SecurityIdentification,

            /// <summary>
            /// 模拟 可以在本地模拟客户端的安全上下文。，但无法在远程系统上模拟客户端；
            /// </summary>
            SecurityImpersonation,

            /// <summary>
            /// 委托 可以在本地和远程系统上模拟客户端的安全上下文
            /// </summary>
            SecurityDelegation
        }

        /// <summary>
        /// 令牌类别
        /// </summary>
        internal enum TokenType
        {
            /// <summary>
            /// 新的令牌是一个主令牌，你可以使用CreateProcessAsUser函数
            /// </summary>
            TokenPrimary = 1,

            /// <summary>
            /// 新的令牌是一个模拟令牌
            /// </summary>
            TokenImpersonation
        }

        #endregion

        #region 系统API 函数导出

        /// <summary>
        /// 获取当前用户会话ID
        /// </summary>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern int WTSGetActiveConsoleSessionId();

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="hServer"></param>
        /// <param name="sessionId"></param>
        /// <param name="pTitle"></param>
        /// <param name="titleLength"></param>
        /// <param name="pMessage"></param>
        /// <param name="messageLength"></param>
        /// <param name="style"></param>
        /// <param name="timeout"></param>
        /// <param name="pResponse"></param>
        /// <param name="bWait"></param>
        /// <returns></returns>
        [DllImport("wtsapi32.dll", SetLastError = true)]
        internal static extern bool WTSSendMessage(
            IntPtr hServer,
            int sessionId,
            string pTitle,
            int titleLength,
            string pMessage,
            int messageLength,
            int style,
            int timeout,
            out int pResponse,
            bool bWait);


        /// <summary>
        /// 关闭句柄
        /// </summary>
        /// <param name="handle"></param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true,
            CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        internal static extern bool CloseHandle(IntPtr handle);

        /// <summary>
        /// 创建用户进程
        /// </summary>
        /// <param name="hToken"></param>
        /// <param name="lpApplicationName"></param>
        /// <param name="lpCommandLine"></param>
        /// <param name="lpProcessAttributes"></param>
        /// <param name="lpThreadAttributes"></param>
        /// <param name="bInheritHandle"></param>
        /// <param name="dwCreationFlags"></param>
        /// <param name="lpEnvrionment"></param>
        /// <param name="lpCurrentDirectory"></param>
        /// <param name="lpStartupInfo"></param>
        /// <param name="lpProcessInformation"></param>
        /// <returns></returns>
        [DllImport("advapi32.dll", SetLastError = true,
            CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
        internal static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            ref SecurityAttributes lpProcessAttributes,
            ref SecurityAttributes lpThreadAttributes,
            bool bInheritHandle,
            int dwCreationFlags,
            IntPtr lpEnvrionment,
            string lpCurrentDirectory,
            ref Startupinfo lpStartupInfo,
            ref ProcessInformation lpProcessInformation);

        /// <summary>
        /// 创建一个新的访问令牌来复制一个已经存在的标记。 这个函数可以创建一个主令牌或模拟令牌 。
        /// </summary>
        /// <param name="hExistingToken"></param>
        /// <param name="dwDesiredAccess"></param>
        /// <param name="lpThreadAttributes"></param>
        /// <param name="impersonationLevel"></param>
        /// <param name="dwTokenType"></param>
        /// <param name="phNewToken"></param>
        /// <returns></returns>
        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool DuplicateTokenEx(
            IntPtr hExistingToken,
            int dwDesiredAccess,
            ref SecurityAttributes lpThreadAttributes,
            int impersonationLevel,
            int dwTokenType,
            ref IntPtr phNewToken);

        /// <summary>
        /// 查询并获取用户令牌
        /// </summary>
        /// <param name="sessionId"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        [DllImport("wtsapi32.dll", SetLastError = true)]
        internal static extern bool WTSQueryUserToken(
            int sessionId,
            out IntPtr token);

        /// <summary>
        /// 创建用户环境
        /// </summary>
        /// <param name="lpEnvironment"></param>
        /// <param name="hToken"></param>
        /// <param name="bInherit"></param>
        /// <returns></returns>
        [DllImport("userenv.dll", SetLastError = true)]
        internal static extern bool CreateEnvironmentBlock(
            out IntPtr lpEnvironment,
            IntPtr hToken,
            bool bInherit);

        /// <summary>
        /// 卸载用户环境
        /// </summary>
        /// <param name="lpEnvironment">用户环境句柄</param>
        /// <returns></returns>
        [DllImport("userenv.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);

        #endregion

        #endregion
    }
}